package org.greatlogic.gwtfilereaderwidget.client;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import org.greatlogic.gwtfilereaderwidget.client.itunes.Filter;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EConjunction;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EOperator;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.ETrackKeyType;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEvent;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEvent.IITunesEventHandler;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesException;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesLibrary;
import org.greatlogic.gwtfilereaderwidget.client.itunes.Playlist;
import org.greatlogic.gwtfilereaderwidget.client.itunes.Track;
import org.greatlogic.gwtfilereaderwidget.client.widget.FileReaderPopupPanel;
import org.greatlogic.gwtfilereaderwidget.client.widget.FileReaderWidgetEvent;
import org.vectomatic.file.File;
import org.vectomatic.file.FileList;
import org.vectomatic.file.FileReader;
import org.vectomatic.file.events.ErrorEvent;
import org.vectomatic.file.events.ErrorHandler;
import org.vectomatic.file.events.LoadEndEvent;
import org.vectomatic.file.events.LoadEndHandler;
import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.SelectionCell;
import com.google.gwt.cell.client.TextInputCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.DefaultSelectionEventManager;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionModel;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.SimpleEventBus;

public class GWTFileReaderWidget implements EntryPoint,
                                FileReaderWidgetEvent.IFileReaderWidgetEventHandler {
//--------------------------------------------------------------------------------------------------
private static final EnumSet<ETrackKeyType> DefaultTrackKeyTypeSet = EnumSet.of(ETrackKeyType.Artist,
                                                                                ETrackKeyType.Album,
                                                                                ETrackKeyType.Name);

@UiField
Button                                      addFilterLineButton;
@UiField
Button                                      applyFilterButton;
@UiField
Button                                      deleteFilterLinesButton;
@UiField
DataGrid<FilterLine>                        filterDataGrid;
@UiField
Button                                      insertFilterLineButton;
@UiField
Button                                      popupPanelButton;
@UiField
Label                                       progressLabel;
@UiField
DataGrid<Track>                             trackDataGrid;

private EventBus                            _eventBus;
private ArrayList<FilterLine>               _filterLineList;
private ITunesLibrary                       _iTunesLibrary;
private ArrayList<ETrackKeyType>            _trackKeyTypeList;
//==================================================================================================
private static class FilterLine {
private String        _closeParens;
private EConjunction  _conjunction;
private ETrackKeyType _keyType;
private String        _openParens;
private String        _operands;
private EOperator     _operator;
public FilterLine() {
  _conjunction = EConjunction.values()[0];
  _keyType = ETrackKeyType.values()[0];
  _operator = EOperator.values()[0];
}
void addToFilter(final Filter filter) throws ITunesException {
  final int openParens = _openParens == null || _openParens.isEmpty() ? 0 : _openParens.length();
  final int closeParens = _closeParens == null || _closeParens.isEmpty() ? 0
                                                                        : _closeParens.length();
  final String[] operands = _operands == null ? null : _operands.split(",");
  filter.addFilterLine(openParens, closeParens, _conjunction, _keyType, _operator, operands);
} // addToFilter()
} // class FilterLine
//==================================================================================================
interface IGWTFileReaderWidgetBinder extends UiBinder<Widget, GWTFileReaderWidget> { //
} // interface IGWTFileReaderWidgetBinder
//==================================================================================================
private void buildFilterCellTable() {
  final SelectionModel<FilterLine> selectionModel = new MultiSelectionModel<FilterLine>();
  filterDataGrid.setSelectionModel(selectionModel,
                                   DefaultSelectionEventManager.<FilterLine> createCheckboxManager());
  // Checkbox (for selection of a row)
  final CheckboxCell selectedCheckbox = new CheckboxCell(true, false);
  final Column<FilterLine, Boolean> selectedCheckboxColumn = new Column<FilterLine, Boolean>(
                                                                                             selectedCheckbox) {
    @Override
    public Boolean getValue(final FilterLine filterLine) {
      return selectionModel.isSelected(filterLine);
    }
  };
  filterDataGrid.addColumn(selectedCheckboxColumn, "");
  // Conjunction
  final ArrayList<String> conjunctionList = new ArrayList<String>();
  conjunctionList.add(EConjunction.And.name());
  conjunctionList.add(EConjunction.Or.name());
  final SelectionCell conjunctionCell = new SelectionCell(conjunctionList);
  final Column<FilterLine, String> conjunctionColumn = new Column<FilterLine, String>(
                                                                                      conjunctionCell) {
    @Override
    public String getValue(final FilterLine filterLine) {
      return filterLine._conjunction.name();
    }
  };
  conjunctionColumn.setFieldUpdater(new FieldUpdater<FilterLine, String>() {
    @Override
    public void update(final int index, final FilterLine filterLine, final String value) {
      filterLine._conjunction = EConjunction.valueOf(value);
    }
  });
  filterDataGrid.addColumn(conjunctionColumn, "And/Or");
  // Open parentheses
  final TextInputCell openParensCell = new TextInputCell();
  final Column<FilterLine, String> openParensColumn = new Column<FilterLine, String>(openParensCell) {
    @Override
    public String getValue(final FilterLine filterLine) {
      return filterLine._openParens;
    }
  };
  openParensColumn.setFieldUpdater(new FieldUpdater<FilterLine, String>() {
    @Override
    public void update(final int index, final FilterLine filterLine, final String value) {
      filterLine._openParens = value;
    }
  });
  filterDataGrid.addColumn(openParensColumn, "(");
  // Key type
  final ArrayList<String> keyTypeList = new ArrayList<String>();
  for (final ETrackKeyType keyType : ETrackKeyType.values()) {
    if (keyType.getElementValue() != null && keyType.getElementValue().length() > 0) {
      keyTypeList.add(keyType.getElementValue());
    }
  }
  final SelectionCell keyTypeCell = new SelectionCell(keyTypeList);
  final Column<FilterLine, String> keyTypeColumn = new Column<FilterLine, String>(keyTypeCell) {
    @Override
    public String getValue(final FilterLine filterLine) {
      return filterLine._keyType == null ? "" : filterLine._keyType.getElementValue();
    }
  };
  keyTypeColumn.setFieldUpdater(new FieldUpdater<FilterLine, String>() {
    @Override
    public void update(final int index, final FilterLine filterLine, final String value) {
      filterLine._keyType = ETrackKeyType.lookupUsingElementValue(value);
    }
  });
  filterDataGrid.addColumn(keyTypeColumn, "Attribute");
  // Operator
  final ArrayList<String> operatorList = new ArrayList<String>();
  for (final EOperator operator : EOperator.values()) {
    if (operator.getOperatorString() != null && operator.getOperatorString().length() > 0) {
      operatorList.add(operator.getOperatorString());
    }
  }
  final SelectionCell operatorCell = new SelectionCell(operatorList);
  final Column<FilterLine, String> operatorColumn = new Column<FilterLine, String>(operatorCell) {
    @Override
    public String getValue(final FilterLine filterLine) {
      return filterLine._operator.getOperatorString();
    }
  };
  operatorColumn.setFieldUpdater(new FieldUpdater<FilterLine, String>() {
    @Override
    public void update(final int index, final FilterLine filterLine, final String value) {
      filterLine._operator = EOperator.lookupUsingOperatorString(value);
    }
  });
  filterDataGrid.addColumn(operatorColumn, "Operator");
  // Operands
  final TextInputCell operandsCell = new TextInputCell();
  final Column<FilterLine, String> operandsColumn = new Column<FilterLine, String>(operandsCell) {
    @Override
    public String getValue(final FilterLine filterLine) {
      return filterLine._operands;
    }
  };
  operandsColumn.setFieldUpdater(new FieldUpdater<FilterLine, String>() {
    @Override
    public void update(final int index, final FilterLine filterLine, final String value) {
      filterLine._operands = value;
    }
  });
  filterDataGrid.addColumn(operandsColumn, "Operands");
  // Close parentheses
  final TextInputCell closeParensCell = new TextInputCell();
  final Column<FilterLine, String> closeParensColumn = new Column<FilterLine, String>(
                                                                                      closeParensCell) {
    @Override
    public String getValue(final FilterLine filterLine) {
      return filterLine._closeParens;
    }
  };
  closeParensColumn.setFieldUpdater(new FieldUpdater<FilterLine, String>() {
    @Override
    public void update(final int index, final FilterLine filterLine, final String value) {
      filterLine._closeParens = value;
    }
  });
  filterDataGrid.addColumn(closeParensColumn, ")");
} // buildFilterCellTable()
//--------------------------------------------------------------------------------------------------
private void buildTrackCellTable() {
  for (final ETrackKeyType trackKeyType : _trackKeyTypeList) {
    final TextColumn<Track> trackColumn = new TextColumn<Track>() {
      @Override
      public String getValue(final Track track) {
        return track.getKeyAndValueEntry(trackKeyType).getValueAsString();
      }
    };
    trackColumn.setSortable(true);
    trackDataGrid.addColumn(trackColumn, trackKeyType.getElementValue());
  }
} // buildTrackCellTable()
//--------------------------------------------------------------------------------------------------
private IITunesEventHandler createITunesEventHandler() {
  return new IITunesEventHandler() {
    @Override
    public void onITunesEvent(final ITunesEvent iTunesEvent) {
      switch (iTunesEvent.getEventType()) {
        case Error:
          Window.alert(iTunesEvent.getMessage());
          break;
        case LoadBegin:
          progressLabel.setText("Load started");
          break;
        case LoadEnd:
          progressLabel.setText("Load finished");
          try {
            final Playlist playlist = _iTunesLibrary.getMasterPlaylist();
            trackDataGrid.setRowData(new ArrayList<Track>(playlist.getTrackSet()));
            trackDataGrid.setVisible(true);
          }
          catch (final ITunesException ite) {
            Window.alert("Failed to get the master playlist:" + ite.getMessage());
          }
          break;
        case PlaylistLoadingBegin:
          progressLabel.setText("Load playlists");
          break;
        case TrackProgress:
          progressLabel.setText("Completed " + iTunesEvent.getCompletedCount() + " tracks");
          break;
      }
    }
  };
} // createITunesEventHandler()
//--------------------------------------------------------------------------------------------------
private LoadEndHandler createLoadEndHandler(final FileReader reader) {
  return new LoadEndHandler() {
    @Override
    public void onLoadEnd(final LoadEndEvent event) {
      if (reader.getError() == null) {
        final String fileContent = reader.getStringResult();
        if (fileContent.length() > 0) {
          _eventBus.addHandler(ITunesEvent.ITunesEventType, createITunesEventHandler());
          Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
              _iTunesLibrary = new ITunesLibrary(_eventBus, 100);
              _iTunesLibrary.load(fileContent);
            }
          });
        }
      }
    }
  };
} // createLoadEndHandler()
//--------------------------------------------------------------------------------------------------
private void loadFile(final File file) {
  final FileReader reader = new FileReader();
  reader.addLoadEndHandler(createLoadEndHandler(reader));
  reader.addErrorHandler(new ErrorHandler() {
    @Override
    public void onError(final ErrorEvent event) {
      Window.alert("Reader error:" + event.getAssociatedType());
    }
  });
  reader.readAsText(file);
} // loadFile()
//--------------------------------------------------------------------------------------------------
@UiHandler("addFilterLineButton")
public void onAddFilterLineButtonClick(final ClickEvent event) {
  _filterLineList.add(new FilterLine());
  refreshFilterCellTable(Integer.MAX_VALUE);
} // onAddFilterLineButtonClick()
//--------------------------------------------------------------------------------------------------
@UiHandler("applyFilterButton")
public void onApplyFilterButtonClick(final ClickEvent event) {
  final Filter filter = new Filter(_iTunesLibrary);
  boolean filterLinesOK = true;
  for (final FilterLine filterLine : _filterLineList) {
    try {
      filterLine.addToFilter(filter);
    }
    catch (final ITunesException ite) {
      filterLinesOK = false;
      Window.alert("Error processing the filter lines:" + ite.getMessage());
    }
  }
  if (filterLinesOK) {
    try {
      final ArrayList<Track> trackList = filter.getTrackList(null, 0);
      trackDataGrid.setRowData(trackList);
    }
    catch (final ITunesException ite) {
      Window.alert("Error applying the filter:" + ite.getMessage());
    }
  }
} // onApplyFilterButtonClick()
//--------------------------------------------------------------------------------------------------
@UiHandler("deleteFilterLinesButton")
public void onDeleteFilterLinesButtonClick(final ClickEvent event) {
  for (final Iterator<FilterLine> filterLineIterator = _filterLineList.iterator(); filterLineIterator.hasNext();) {
    final FilterLine filterLine = filterLineIterator.next();
    if (filterDataGrid.getSelectionModel().isSelected(filterLine)) {
      filterLineIterator.remove();
    }
  }
  refreshFilterCellTable(Integer.MAX_VALUE);
} // onDeleteFilterLinesButtonClick()
//--------------------------------------------------------------------------------------------------
@Override
public void onFileReaderWidgetEvent(final FileReaderWidgetEvent event) {
  final FileList fileList = event.getFileList();
  if (fileList.getLength() == 1) {
    loadFile(fileList.getItem(0));
  }
  else {
    Window.alert("Only one file can be selected");
  }
} // onFileReaderWidgetEvent()
//--------------------------------------------------------------------------------------------------
@UiHandler("insertFilterLineButton")
public void onInsertFilterLineButtonClick(final ClickEvent event) {
  final int rowIndex = filterDataGrid.getKeyboardSelectedRow();
  _filterLineList.add(rowIndex, new FilterLine());
  refreshFilterCellTable(rowIndex);
} // onAddFilterLineButtonClick()
//--------------------------------------------------------------------------------------------------
@Override
public void onModuleLoad() {
  _filterLineList = new ArrayList<GWTFileReaderWidget.FilterLine>();
  _trackKeyTypeList = new ArrayList<ETrackKeyType>(DefaultTrackKeyTypeSet);
  _eventBus = new SimpleEventBus();
  _eventBus.addHandler(FileReaderWidgetEvent.FileWidgetReaderEventType, this);
  final IGWTFileReaderWidgetBinder binder = GWT.create(IGWTFileReaderWidgetBinder.class);
  final Widget widget = binder.createAndBindUi(this);
  buildFilterCellTable();
  refreshFilterCellTable(0);
  buildTrackCellTable();
  trackDataGrid.setVisible(false);
  RootLayoutPanel.get().add(widget);
} // onModuleLoad()
//--------------------------------------------------------------------------------------------------
@UiHandler("popupPanelButton")
public void onPopupPanelButtonClick(final ClickEvent event) {
  FileReaderPopupPanel.requestFile("Drop iTunes library file here", false, _eventBus);
} // onPopupPanelButtonClick()
//--------------------------------------------------------------------------------------------------
private void refreshFilterCellTable(final int selectedRowIndex) {
  if (_filterLineList.size() == 0) {
    _filterLineList.add(new FilterLine());
  }
  filterDataGrid.setRowData(_filterLineList);
  filterDataGrid.redraw();
  final int rowIndex = selectedRowIndex >= _filterLineList.size() ? _filterLineList.size() - 1
                                                                 : selectedRowIndex;
  filterDataGrid.setKeyboardSelectedRow(rowIndex);
  filterDataGrid.setKeyboardSelectedColumn(0, true);
} // refreshFilterCellTable()
//--------------------------------------------------------------------------------------------------
}